/* Copyright (c) 2009, Enric Rodriguez (enrodri86@gmail.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 	- Redistributions of source code must retain the above copyright
 * 	notice,	this list of conditions and the following disclaimer.
 * 	- Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * 	- Neither the name of the <ORGANIZATION> nor the names of its
 * 	contributors may be used to endorse or promote products derived from
 * 	this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package es.enrodri86.mmo.auth.server;

import java.util.Hashtable;

import es.enrodri86.mmo.auth.shared.AuthException;
import es.enrodri86.mmo.auth.shared.Captcha;
import es.enrodri86.mmo.auth.shared.LoginException;

public abstract class BasicHTTPAuthServer implements AuthServer {

	private Hashtable<String,Long> activity = new Hashtable<String,Long>();
	private Hashtable<String,String> ipaddr = new Hashtable<String, String>();
	private Hashtable<String,Captcha> captcha = new Hashtable<String, Captcha>();
	private Hashtable<String,Object> ubinding = new Hashtable<String, Object>();
	private boolean busy;


	public void releaseKey(String session){
		activity.remove(session);
		ipaddr.remove(session);
		captcha.remove(session);
		ubinding.remove(session);
	}

	public String getIPAddress(String session){
		return ipaddr.get(session);
	}

	public Object getUserBinding(String session){
		return ubinding.get(session);
	}

	/**
	 * If you set the {@link AuthServer} as busy, no new connections will be
	 * accepted. This is useful if you don't want to allow new players to log
	 * in while in maintenance or any other reason.
	 * @param busy
	 */
	public void setBusy(boolean busy) {
		this.busy = busy;
	}

	public boolean isBusy() {
		return busy;
	}

	public void connect(String sessionKey, String ip) throws AuthException {
		if(isBusy()){
			AuthException aex = new AuthException();
			aex.setDescription("Server is too busy, please try again soon.");
			throw aex;
		}

		if(activity.containsKey(sessionKey)){
			activity.put(sessionKey, System.currentTimeMillis());
		}else{
			AuthException ae = new AuthException();
			ae.setDescription("Invalid session key. Please try again.");
			throw ae;
		}
		ipaddr.put(sessionKey, ip);
		checkIPAddress(ip);
		captcha.put(sessionKey, prepareCaptcha(sessionKey));

	}

	protected abstract Captcha prepareCaptcha(String sessionKey);

	/**
	 * Use this method to check wether this IP address is banned or not.
	 * @param ip
	 * @throws AuthException
	 */
	protected abstract void checkIPAddress(String ip) throws AuthException;


	public Captcha getCaptcha(String sessionKey) {
		if(activity.containsKey(sessionKey)){
			activity.put(sessionKey, System.currentTimeMillis());
		}else{
			return null;
		}
		return captcha.get(sessionKey);
	}

	/**
	 * Generates a new unique session.
	 */
	public String getSession() {
		String chars = "1234567890abcdef";
		String session = "";
		for(int i = 0; i < 32; i++){
			session += chars.charAt((int)(Math.random()*((double)chars.length())));
		}
		if(activity.containsKey(session)) return getSession();
		activity.put(session, System.currentTimeMillis());
		return session;
	}

	public boolean isCaptchaRequired(String sessionKey) {
		if(activity.containsKey(sessionKey)){
			activity.put(sessionKey, System.currentTimeMillis());
		}else{
			return false;
		}
		if(captcha.get(sessionKey) != null && (captcha.get(sessionKey).isAudioCaptcha() || captcha.get(sessionKey).isVisualCaptcha())) return true;
		else return false;
	}


	public boolean login(String sessionKey, String user, String pass,
			String ctext) throws LoginException {
		if(activity.containsKey(sessionKey)){
			activity.put(sessionKey, System.currentTimeMillis());
		}else{
			captcha.remove(sessionKey);
			return false;
		}
		if(isCaptchaRequired(sessionKey)){
			Captcha c = captcha.get(sessionKey);
			if(ctext == null || (!ctext.equals(c.getVisualCaptchaSolution()) && !ctext.equals(c.getAudioCaptchaSolution()))){
				LoginException le = new LoginException();
				le.setCode(LoginException.TYPE_INVALID_CAPTCHA);
				le.setDescription("You have typed an incorrect answer for your CAPTCHA test.");
				captcha.remove(sessionKey);
				throw le;
			}
		}

		Object ub = checkLogin(user,pass);
		if(ubinding.containsValue(ub)){
			LoginException le = new LoginException();
			le.setCode(LoginException.TYPE_ALREADY_PLAYING);
			le.setDescription("You are still playing the game.");
			captcha.remove(sessionKey);
			throw le;
		}
		ubinding.put(sessionKey, ub);
		captcha.remove(sessionKey);
		return true;
	}

	/**
	 * Check this user's password at the database. Throw the appropiate
	 * exception if needed. In case the password is correct do not throw any
	 * excpetion, and return a object identifying the user. This object will be
	 * sent to the game server along with the session, this way the server will
	 * know who is connected with that session.
	 * @param user
	 * @param pass
	 * @return
	 * @throws LoginException
	 */
	protected abstract Object checkLogin(String user, String pass) throws LoginException;

}
